﻿using System;
using System.Collections.Generic;
using System.Linq;
using Algoritmia.Utils;

namespace Algoritmia.DataStructures.Maps
{
    public class HashMap<TKey,TValue> : 
        IMap<TKey,TValue>
    {
        protected int _count;
       
        protected readonly Lists.IList<KeyValuePair<TKey, TValue>>[] _supportList;
        protected readonly int _capacity;
        protected readonly Func<Lists.IList<KeyValuePair<TKey, TValue>>> _createContainedLists;

        private readonly KeysView<TKey,TValue> _keysView;
        private readonly ItemsView<TKey, TValue> _itemsView;
        private readonly ValuesView<TKey, TValue> _valuesView;

        private readonly EqualityComparison<TKey> _keysComparison;

        public HashMap(IEnumerable<KeyValuePair<TKey,TValue>> items=null, 
                       int capacity=16, EqualityComparison<TKey> keysEqualityComparison = null,
                       Func<Lists.IList<KeyValuePair<TKey,TValue>>> createContainedLists=null)
        {
            items = items ?? Enumerable.Empty<KeyValuePair<TKey, TValue>>();
            _createContainedLists = createContainedLists ?? (() => new Lists.LinkedList<KeyValuePair<TKey,TValue>>());
            _count = 0;
            _keysComparison = AlgoritmiaFactory.GetDefaultEqualityComparison(keysEqualityComparison);
            int nElems;
         
            if ( !(items is ICountable))
            {
                var tmpList = items.ToList();
                nElems = tmpList.Count;
                items = tmpList;
            }
            else
            {
                nElems = ((ICountable)items).Count;
            }
            _capacity = nElems > capacity ? nElems : capacity;

            if (_capacity == 0) _capacity = 1;

            _supportList = new Lists.IList<KeyValuePair<TKey, TValue>>[_capacity];
            foreach (var kv in items)
            {
                this[kv.Key] = kv.Value;
            }

            _keysView = new KeysView<TKey, TValue>(this);
            _itemsView = new ItemsView<TKey, TValue>(this);
            _valuesView = new ValuesView<TKey, TValue>(this);
        }

        private int GetHashFromKeyCode(TKey key)
        {
           return Math.Abs(key.GetHashCode() % _capacity);
        }

        #region IMap<TKey,TValue> Members

        public virtual TValue this[TKey key]
        {
            get
            {
                var hash = GetHashFromKeyCode(key);
                if (_supportList[hash] != null)
                {
                    foreach (var ekv in _supportList[hash])
                    {
                        if (_keysComparison(ekv.Key,key))
                        {
                            return ekv.Value;
                        }
                    }
                }
                throw new KeyNotFoundException(String.Format("Key {0} is not contained", key));
            }
            set
            {
                var hash = GetHashFromKeyCode(key);
                if (_supportList[hash] == null)
                    _supportList[hash] = _createContainedLists();
                int i = 0;
                foreach(var kv in _supportList[hash])
                {
                    if ( _keysComparison(kv.Key,key) )
                    {
                        _supportList[hash].RemoveAt(i);
                        _supportList[hash].Add(new KeyValuePair<TKey, TValue>(key, value));
                        return;
                    }
                    i++;
                }
                _supportList[hash].Add(new KeyValuePair<TKey, TValue>(key, value));
                _count++;
            }
        }

        public virtual void Add(TKey key, TValue value)
        {
            this[key] = value;
        }

        public bool Contains(TKey key)
        {
            var hash = GetHashFromKeyCode(key);
            if (_supportList[hash] == null)
            {
                return false;
            }
            return (from kv in _supportList[hash]
                    select kv.Key).Any(k => _keysComparison(k, key));
        }

        public TValue Get(TKey key, TValue defaultValue)
        {
            var hash = GetHashFromKeyCode(key);
            if (_supportList[hash] != null)
            {
                foreach (var kv in _supportList[hash])
                {
                    if ( _keysComparison(kv.Key,key) )
                    {
                        return kv.Value;
                    }
                }
            }
            return defaultValue;
        }

        public ICollectionView<TKey> Keys()
        {
            return _keysView;
        }

        public ICollectionView<KeyValuePair<TKey, TValue>> Items()
        {
            return _itemsView;
        }

        public virtual bool Remove(TKey key)
        {
            var hash = GetHashFromKeyCode(key);
            if (_supportList[hash] != null)
            {
                int i = 0;
                foreach(var kv in _supportList[hash])
                {
                    if (_keysComparison(kv.Key,key))
                    {
                        _supportList[hash].RemoveAt(i);
                        _count--;
                        return true;
                    }
                    i++;
                }
            }
            return false;
        }

        public TValue SetDefault(TKey key, TValue defaultValue)
        {
            var hash = GetHashFromKeyCode(key);
            if (_supportList[hash] != null)
            {
                foreach (var kv in _supportList[hash])
                {
                    if ( _keysComparison(kv.Key,key))
                    {
                        return kv.Value;
                    }
                }
            }
            this[key] = defaultValue;
            return defaultValue;
        }

        public ICollectionView<TValue> Values()
        {
            return _valuesView;
        }

        #endregion

        #region IEnumerable<KeyValuePair<TKey,TValue>> Members

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            for (var i = 0; i < _capacity; i++)
            {
                if (_supportList[i] != null)
                {
                    foreach (var kv in _supportList[i])
                        yield return new KeyValuePair<TKey, TValue>(kv.Key, kv.Value);
                }
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region ICountable Members

        public int Count
        {
            get { return _count; }
        }

        #endregion

        public override string ToString()
        {
            return String.Format("{0} <{1},{2}> [{3}]", GetType().Name, typeof(TKey).Name, typeof(TValue).Name,string.Join(",",this));
        }
    }
}
